//=========================================================================
// Copyright (C) 2024 The C++ Component Model(COMO) Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//=========================================================================

/**
 * Persist Memory allocator
 */

#include "pmfunction.h"

/**
 * 文件复用的情况下，需要把内存池管理结构中上一次的相关地址换成本次remap后的地址
 * 仅在pmfunction.cpp : reuse_mempool 中被调用
 */
void rebase_mdesc_infos(struct mdesc *mdp, void *e_addr, void *o_addr)
{
    if (e_addr == o_addr) {
        return;
    }
    printf("Rebase Address from %p to %p \n", o_addr, e_addr);
    // sleep(5);
    ptrdiff_t off;
    off = static_cast<char *>(e_addr) - static_cast<char *>(o_addr);
    assert(mdp != nullptr);

    // 根据前后两次baseaddr的offset去重新设置地址数据
    mdp->mempoolbase = static_cast<char *>(e_addr);
    mdp->mblkinfo = static_cast<malloc_info *>(REBASE_ADDRESS(mdp->mblkinfo, off));
    mdp->watermarkpos = static_cast<char *>(REBASE_ADDRESS(mdp->watermarkpos, off));
    mdp->limitpos = static_cast<char *>(REBASE_ADDRESS(mdp->limitpos, off));
    mdp->mblkinfobase = static_cast<char *>(REBASE_ADDRESS(mdp->mblkinfobase, off));

    size_t idx;

    /* not necessary to rebase all persistent key pointer since user do it
     instead more elegantly. for (idx = 0; idx < PMALLOC_KEYS; ++idx) { if (nullptr
     != mdp->keys[idx]) { mdp->keys[idx] = REBASE_ADDRESS(mdp->keys[idx], off);
     }
     }
     */

    struct alignlist **pal = &mdp->aligned_blocks;
    while (nullptr != *pal) {
        *pal = static_cast<alignlist *>(REBASE_ADDRESS(*pal, off));
        pal = &(*pal)->next;
    }

    for (idx = 0;  idx < BLOCKLOG;  idx++) {
        struct list *plist = &mdp->fragblockhead[idx];
        while (nullptr != plist) {
            if (nullptr != plist->next) {
                plist->next = static_cast<list *>(REBASE_ADDRESS(plist->next, off));
            }
            if (nullptr != plist->prev) {
                plist->prev = static_cast<list *>(REBASE_ADDRESS(plist->prev, off));
            }
            plist = plist->next;
        }
    }
}

/**
 * 针对之前被用过初始化内存池的文件，进行读取管理结构而复用
 */
struct mdesc *reuse_mempool(const int fd)
{
    struct mdesc mtemp;
    struct mdesc *mdp = nullptr;

    if ((lseek(fd, 0L, SEEK_SET) == 0) &&
            (read(fd, reinterpret_cast<char *>(&mtemp), sizeof(mtemp)) == sizeof(mtemp)) &&
            (mtemp.headersize == sizeof(mtemp)) &&
            (strcmp(mtemp.magicwords, PMALLOC_MAGIC) == 0) &&
            (mtemp.version <= PMALLOC_VERSION)) {
        mtemp.mappingfd = fd;

        void *remap_base = __pmalloc_remap_mempool(&mtemp);
        if (remap_base != nullptr) {
            mdp = static_cast<struct mdesc *>(remap_base);
            rebase_mdesc_infos(mdp, remap_base, mdp->mempoolbase);
            mdp->mappingfd = fd;
            mdp->morespace = __pmalloc_map_morespace;
        }
    }
    return (mdp);
}

/**
 * 在经过页对齐后的起始地址上进行内存申请
 */
void *align(struct mdesc *mdp, const size_t size)
{
    void *result;
    unsigned long int adj;

    // 页对齐和连续空间，为了页对齐会产生内存碎片
    result = mdp->morespace(mdp, size);
    adj = RESIDUAL(result, BLOCKSIZE);
    if (adj != 0) {
        adj = BLOCKSIZE - adj;
        mdp->morespace(mdp, adj);
        result = static_cast<char *>(result) + adj;
    }
    return (result);
}

/**
 * 内存池中块管理结构进行初始化
 * 仅在pmalloc.cpp : pmalloc中判断调用
 */
int initialize(struct mdesc *mdp)
{
    mdp->mblkinfosize = INITMPOOLSIZE / BLOCKSIZE + 1;
    mdp->mblkinfo =
            static_cast<malloc_info *>(align(mdp, mdp->mblkinfosize * sizeof(malloc_info)));
    if (nullptr == mdp->mblkinfo) {
        return (0);
    }
    memset(static_cast<void *>(mdp->mblkinfo), 0, mdp->mblkinfosize * sizeof(malloc_info));
    mdp->mblkinfo[0].free.size = 0;
    mdp->mblkinfo[0].free.next = 0;
    mdp->mblkinfo[0].free.prev = 0;
    mdp->mblkinfosearchindex = 0;
    mdp->mblkinfobase = reinterpret_cast<char *>(mdp->mblkinfo);
    mdp->flags |= PMALLOC_INITIALIZED;
    return (1);
}

/**
 * pmfunction.cpp内部使用的块概念的空间拓展
 */
void *morespace(struct mdesc *mdp, const size_t size)
{
    void *result;
    malloc_info *newinfo, *oldinfo;
    size_t newsize;

    result = align(mdp, size);
    if (nullptr == result) {
        return (nullptr);
    }

    /**
     * 如果本次申请的内存块数量大于目前可用的管理结构数量，会重新申请管理结构内存区域，
     * 复制内容后释放原内存区域
     */
    if (static_cast<size_t>(BLOCK(static_cast<char *>(result) + size)) > mdp->mblkinfosize) {
        newsize = mdp->mblkinfosize + 1;
        while (static_cast<size_t>(BLOCK(static_cast<char *>(result) + size)) > newsize) {
            newsize *= 2;
        }

        newinfo = static_cast<malloc_info *>(align(mdp, newsize * sizeof(malloc_info)));
        if (nullptr == newinfo) {
            mdp->morespace(mdp, -size);
            return (nullptr);
        }

        const size_t n = mdp->mblkinfosize * sizeof(malloc_info);
        memcpy(static_cast<void *>(newinfo), static_cast<void *>(mdp->mblkinfo), n);
        if (newsize > mdp->mblkinfosize) {
            memset(reinterpret_cast<char *>(newinfo) + n, 0, newsize * sizeof(malloc_info) - n);
        }

        oldinfo = mdp->mblkinfo;
        newinfo[BLOCK(oldinfo)].inuse.fragtype = 0;
        newinfo[BLOCK(oldinfo)].inuse.info.sizeinblock =
                              BLOCKIFY(mdp->mblkinfosize * sizeof(malloc_info));
        mdp->mblkinfo = newinfo;
        __pmalloc_free(mdp, static_cast<void *>(oldinfo));  // 释放原先管理内存块的结构区域内存
        mdp->mblkinfosize = newsize;
    }

    mdp->mblkinfoidxlimit = BLOCK(static_cast<char *>(result) + size);
    return (result);
}

void *allocate_blockfrag(struct mdesc *mdp, size_t size)
{
    void *result = nullptr;
    size_t block;
    size_t i;
    struct list *next;
    size_t log;

    log = 1;
    --size;
    while ((size /= 2) != 0) {
        ++log;
    }

    next = mdp->fragblockhead[log].next;
    if (next != nullptr) {
        result = static_cast<void *>(next);
        next->prev->next = next->next;
        if (next->next != nullptr) {
            next->next->prev = next->prev;
        }
        block = BLOCK(result);
        if (--mdp->mblkinfo[block].inuse.info.frag.nfreefrags != 0) {
            mdp->mblkinfo[block].inuse.info.frag.firstfragidxinlist =
                                         RESIDUAL(next->next, BLOCKSIZE) >> log;
        }

        mdp->mblkstats.chunks_used++;
        mdp->mblkstats.bytes_used += static_cast<size_t>(1 << log);
        mdp->mblkstats.chunks_free--;
        mdp->mblkstats.bytes_free -= static_cast<size_t>(1 << log);
    }
    else {
        result = allocate_blocks(mdp, 1);
        if (result != nullptr) {
            for (i = 1;  i < static_cast<size_t>(BLOCKSIZE >> log);  i++) {
                next = reinterpret_cast<struct list *>(static_cast<char *>(result) + (i << log));
                next->next = mdp->fragblockhead[log].next;
                next->prev = &mdp->fragblockhead[log];
                next->prev->next = next;
                if (next->next != nullptr) {
                    next->next->prev = next;
                }
            }

            block = BLOCK(result);
            mdp->mblkinfo[block].inuse.fragtype = static_cast<int>(log);
            mdp->mblkinfo[block].inuse.info.frag.nfreefrags = static_cast<size_t>(i - 1);
            mdp->mblkinfo[block].inuse.info.frag.firstfragidxinlist = static_cast<size_t>(i - 1);

            mdp->mblkstats.chunks_free += static_cast<size_t>((BLOCKSIZE >> log) - 1);
            mdp->mblkstats.bytes_free += BLOCKSIZE - static_cast<unsigned int>(1 << log);
            mdp->mblkstats.bytes_used -= BLOCKSIZE - static_cast<unsigned int>(1 << log);
        }
    }

    return result;
}

/**
 * 分配内存块
 */
void *allocate_blocks(struct mdesc *mdp, const size_t blocks)
{
    void *result = nullptr;
    size_t lastblocks;

    const size_t start = MALLOC_SEARCH_START;
    size_t block = MALLOC_SEARCH_START;

    while (mdp->mblkinfo[block].free.size < blocks) {
        block = mdp->mblkinfo[block].free.next;
        if (block == start) {
            block = mdp->mblkinfo[0].free.prev;
            lastblocks = mdp->mblkinfo[block].free.size;

            // 首次分配会跳过该分支，进行下面的morespace初始化+初次分配
            if ((mdp->mblkinfoidxlimit != 0) &&
                    ((block + lastblocks) == mdp->mblkinfoidxlimit) &&
                    (mdp->morespace(mdp, 0) == ADDRESS(block + lastblocks)) &&
                    ((morespace(mdp, (blocks - lastblocks) * BLOCKSIZE)) != nullptr)) {
                block = mdp->mblkinfo[0].free.prev;

                mdp->mblkinfo[block].free.size += (blocks - lastblocks);
                mdp->mblkstats.bytes_free += (blocks - lastblocks) * BLOCKSIZE;
                continue;
            }
            result = morespace(mdp, blocks * BLOCKSIZE);
            if (result != nullptr) {
                block = BLOCK(result);
                mdp->mblkinfo[block].inuse.fragtype = 0;
                mdp->mblkinfo[block].inuse.info.sizeinblock = blocks;
                mdp->mblkstats.chunks_used++;
                mdp->mblkstats.bytes_used += blocks * BLOCKSIZE;
            }
            return (result);
        }
    }

    result = ADDRESS(block);
    malloc_info *tmp = &(mdp->mblkinfo[block]);

    if (tmp->free.size > blocks) {
        const size_t block2 = block + blocks;
        malloc_info *tmp2 = &(mdp->mblkinfo[block2]);

        tmp2->free.size = tmp->free.size - blocks;
        tmp2->free.next = tmp->free.next;
        tmp2->free.prev = tmp->free.prev;

        mdp->mblkinfo[tmp->free.prev].free.next = block2;
        mdp->mblkinfo[tmp->free.next].free.prev = block2;
        mdp->mblkinfosearchindex = block2;
    }
    else {
        mdp->mblkinfo[tmp->free.next].free.prev = tmp->free.prev;
        mdp->mblkinfo[tmp->free.prev].free.next = tmp->free.next;
        mdp->mblkinfosearchindex = tmp->free.next;
        mdp->mblkstats.chunks_free--;
    }

    tmp->inuse.fragtype = 0;
    tmp->inuse.info.sizeinblock = blocks;
    mdp->mblkstats.chunks_used++;
    mdp->mblkstats.bytes_used += blocks * BLOCKSIZE;
    mdp->mblkstats.bytes_free -= blocks * BLOCKSIZE;
    return result;
}

void free_blocks(struct mdesc *mdp, size_t block)
{
    size_t blocks;
    size_t i;
    malloc_info *tmp = &(mdp->mblkinfo[block]);

    mdp->mblkstats.chunks_used--;
    mdp->mblkstats.bytes_used -= tmp->inuse.info.sizeinblock * BLOCKSIZE;
    mdp->mblkstats.bytes_free += tmp->inuse.info.sizeinblock * BLOCKSIZE;

    i = mdp->mblkinfosearchindex;
    if (i > block) {
        while (i > block) {
            i = mdp->mblkinfo[i].free.prev;
        }
    }
    else {
        do {
            i = mdp->mblkinfo[i].free.next;
        } while ((i != 0) && (i < block));

        i = mdp->mblkinfo[i].free.prev;
    }

    if (block == (i + mdp->mblkinfo[i].free.size)) {
        mdp->mblkinfo[i].free.size += tmp->inuse.info.sizeinblock;
        block = i;
        tmp = &(mdp->mblkinfo[block]);
    }
    else {
        tmp->free.size = tmp->inuse.info.sizeinblock;
        tmp->free.next = mdp->mblkinfo[i].free.next;
        tmp->free.prev = i;
        mdp->mblkinfo[i].free.next = block;
        mdp->mblkinfo[tmp->free.next].free.prev = block;
        mdp->mblkstats.chunks_free++;
    }

    if ((block + tmp->free.size) == tmp->free.next) {
        tmp->free.size += mdp->mblkinfo[tmp->free.next].free.size;
        tmp->free.next = mdp->mblkinfo[tmp->free.next].free.next;
        mdp->mblkinfo[tmp->free.next].free.prev = block;
        mdp->mblkstats.chunks_free--;
    }

    blocks = tmp->free.size;
    if ((blocks >= FINAL_FREE_BLOCKS) &&
                          (block + blocks == mdp->mblkinfoidxlimit) &&
                          (mdp->morespace(mdp, 0) == ADDRESS(block + blocks))) {

        const size_t bytes = blocks * BLOCKSIZE;

        mdp->mblkinfoidxlimit -= blocks;
        mdp->morespace(mdp, -bytes);
        mdp->mblkinfo[tmp->free.prev].free.next = tmp->free.next;
        mdp->mblkinfo[tmp->free.next].free.prev = tmp->free.prev;
        block = tmp->free.prev;
        mdp->mblkstats.chunks_free--;
        mdp->mblkstats.bytes_free -= bytes;
    }

    mdp->mblkinfosearchindex = block;
}

void free_blockfrag(struct mdesc *mdp, const size_t block, const int fraglog, void *addr)
{
    // size_t blocks;
    size_t i;
    struct list *prev, *next;

    mdp->mblkstats.chunks_used--;
    mdp->mblkstats.bytes_used -= static_cast<size_t>(1 << fraglog);
    mdp->mblkstats.chunks_free++;
    mdp->mblkstats.bytes_free += static_cast<size_t>(1 << fraglog);

    prev =
        reinterpret_cast<struct list *>(static_cast<char *>(ADDRESS(block)) +
                        (mdp->mblkinfo[block].inuse.info.frag.firstfragidxinlist
                         << fraglog));

    if (mdp->mblkinfo[block].inuse.info.frag.nfreefrags ==
                                                  static_cast<size_t>((BLOCKSIZE >> fraglog) - 1)) {
        next = prev;
        for (i = 1;  i < static_cast<size_t>(BLOCKSIZE >> fraglog);  ++i) {
            next = next->next;
        }
        prev->prev->next = next;
        if (next != nullptr) {
            next->prev = prev->prev;
        }
        mdp->mblkinfo[block].inuse.fragtype = 0;
        mdp->mblkinfo[block].inuse.info.sizeinblock = 1;

        mdp->mblkstats.chunks_used++;
        mdp->mblkstats.bytes_used += BLOCKSIZE;
        mdp->mblkstats.chunks_free -= BLOCKSIZE >> fraglog;
        mdp->mblkstats.bytes_free -= BLOCKSIZE;

        pmfree(static_cast<void *>(mdp), static_cast<void *>(ADDRESS(block)));
    }
    else if (mdp->mblkinfo[block].inuse.info.frag.nfreefrags != 0) {
        next = static_cast<list *>(addr);
        next->next = prev->next;
        next->prev = prev;
        prev->next = next;
        if (next->next != nullptr) {
            next->next->prev = next;
        }
        ++mdp->mblkinfo[block].inuse.info.frag.nfreefrags;
    }
    else {
        prev = static_cast<list *>(addr);
        mdp->mblkinfo[block].inuse.info.frag.nfreefrags = 1;
        mdp->mblkinfo[block].inuse.info.frag.firstfragidxinlist =
                                           RESIDUAL(addr, BLOCKSIZE) >> fraglog;
        prev->next = mdp->fragblockhead[fraglog].next;
        prev->prev = &mdp->fragblockhead[fraglog];
        prev->prev->next = prev;
        if (prev->next != nullptr) {
            prev->next->prev = prev;
        }
    }
}

void *pmopen(const char *fn, void *baseaddr, const size_t initial_size)
{
    void *ret = nullptr;
    assert(nullptr != fn);
    const int fd = open(fn, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
    if (fd >= 0) {
        ret = pmalloc_attach(fd, baseaddr, initial_size, false);
    }
    return ret;
}

long pmcapacity(void *md)
{
    struct mdesc *mdp = static_cast<struct mdesc *>(md);
    if ((nullptr == mdp->limitpos) || (nullptr == mdp->mempoolbase) ||
                                          (mdp->limitpos <= mdp->mempoolbase)) {
        return 0L;
    }
    return (mdp->limitpos - mdp->mempoolbase);
}

void pmclose(void *md)
{
    struct mdesc *mdp = static_cast<struct mdesc *>(md);
    assert(nullptr != mdp);
    // pmalloc_detach(md);
    close(mdp->mappingfd);
}
